package mini;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import mini.expr.Element;
import mini.expr.Expr;
import mini.statement.ReturnException;


public class Proc extends Expr {

    private ParamList parameterlist;
    private StatementList stmtlist;
    private String name;
    
	public Proc(String str, ParamList pl, StatementList sl) {
		name = str;
        parameterlist = pl;
        stmtlist = sl;
    }

    public Element apply(List<Env> listOfEnvs, LinkedList var, ExpressionList expressionlist) {

    	// New environment created (as we at least need to set parameters!)
    	Env env = new Env();
    	
        // bind parameters in new name table
        // we need to get the underlying List structure that the ParamList uses...
    	List<String> parameters = new ArrayList<String>();
    	List<Expr> expressions = new ArrayList<Expr>();
    	if( parameterlist != null && parameterlist.getParamList() != null ) {
    		parameters = parameterlist.getParamList();
    	}
    	if( expressionlist != null && expressionlist.getExpressions() != null ) {
    		expressions = expressionlist.getExpressions();
    	}
    	
        Iterator<String> p = parameters.iterator();
        Iterator<Expr> e = expressions.iterator();

        if (parameters.size() != expressions.size()) {
            System.out.println("Param count does not match for " + name + "; had " + parameterlist.getParamList().size() + " parameters but " +
            		expressionlist.getExpressions().size() + " values");
            System.exit(1);
        }
        
        // Set the parameters
        while (p.hasNext() && e.hasNext()) {
            env.addToNametable(p.next(), e.next().eval(listOfEnvs, var));
        }
        
        // Now, copy over the chain of variables
        List<Env> envsForProc = new ArrayList<Env>();
    	for( Env tempEnv : listOfEnvs ) {
    		envsForProc.add(tempEnv);
    	}
        
        // We always have to add the current env with he parameters
        envsForProc.add(env);
        
        //System.out.println("Beginning Proceedure Execution..");
        try {
            stmtlist.eval(envsForProc, var);
        } catch (Exception result) {
        	if( result instanceof ReturnException ) {
            	return ((ReturnException)result).getElement();
        	}
        	
        	throw new RuntimeException(result);
        }
        System.out.println("Error:  no return value");
        System.exit(1);
        // need this or the compiler will complain, but should never
        // reach this...
        return null;
    }
    
    /**
     * If we're evaluating it, we just are returning an element that encapsulates
     * the procedure.
     * @param nametable
     * @param functiontable
     * @param var
     * @param expressionlist
     * @return
     */
    public Element eval(List<Env> listOfEnvs, LinkedList var) {
    	return new Element(this);
    }


    @Override
    public String toString() {  
    	return "[Proc name=" + name + ", " + 
    			(parameterlist == null ? 0 : (parameterlist.getParamList() == null ? 0 : parameterlist.getParamList().size() ) )  + 
    			" params, " +
    			(stmtlist == null ? 0 : (stmtlist.getStatements() == null ? 0 : stmtlist.getStatements().size() ) ) +
    			" statements]";
    }
    
}